/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#ifndef CORE_PROFILE_DEFAULTPROFILE_H_
#define CORE_PROFILE_DEFAULTPROFILE_H_

#include <string>
#include <vector>

#include "auth/HmacSha1.h"
#include "auth/ISigner.h"
#include "auth/Credential.h"
#include "auth/ICredentialProvider.h"
#include "http/FormatType.h"
#include "regions/Endpoint.h"
#include "regions/InternalEndpointsParser.h"
#include "IClientProfile.h"

#include <stdio.h>
#include <iostream>

namespace aliyun {

namespace profile {

class DefaultProfile : public IClientProfile {
 public:
  typedef auth::HmacSha1 HmacSha1;
  typedef auth::ICredentialProvider ICredentialProvider;
  typedef regions::ProductDomain ProductDomain;
  typedef regions::InternalEndpointsParser InternalEndpointsParser;
  typedef regions::IEndpointsProvider IEndpointsProvider;

  ISigner* getSigner() {
    return isigner_;
  }

  FormatType getFormat() {
    return acceptFormat_;
  }

  const Credential& getCredential() {
    if (!refreshed_ && icredential_ != NULL) {
      credential_ = icredential_->fresh();
      refreshed_ = true;
    }
    return credential_;
  }

  string getRegionId() {
    return regionId_;
  }

  std::vector<Endpoint> getEndpoints() throw (ClientException) {
    if (sEndpoints_.size() == 0) {
      return iendpoints_->getEndpoints();
    }
    return sEndpoints_;
  }

  // singleton usage.
  static DefaultProfile* getProfile() {
    if (sProfile_ == NULL) {
      static DefaultProfile stub;
      sInstanceCache_ = stub;
      sProfile_ = &sInstanceCache_;
    }
    return sProfile_;
  }

  static DefaultProfile* getProfile(string regionId,
                                    ICredentialProvider* icredential) {
    sInstanceCache_ = DefaultProfile(regionId, icredential);
    sProfile_ = &sInstanceCache_;
    return sProfile_;
  }

  static DefaultProfile* getProfile(string regionId, string accessKeyId,
                                    string secret) {
    Credential creden(accessKeyId, secret);
    sInstanceCache_ = DefaultProfile(regionId, creden);
    sProfile_ = &sInstanceCache_;
    return sProfile_;
  }

  static void addEndpoint(string endpointName, string regionId, string product,
                          string domain) throw (Exception) {
    if (sEndpoints_.size() == 0) {
      sEndpoints_ = getProfile()->getEndpoints();
    }
    Endpoint* endpoint = findEndpointByRegionId(regionId);
    if (NULL != endpoint) {
      updateEndpoint(regionId, product, domain, endpoint);
    } else {
      addToEndpoints(endpointName, regionId, product, domain);
    }
  }

  static void addToEndpoints(string endpointName, string regionId,
                             string product, string domain) {
    std::set<string> regionIds;
    regionIds.insert(regionId);

    std::vector<ProductDomain> productDomains;
    productDomains.push_back(ProductDomain(product, domain));

    sEndpoints_.push_back(Endpoint(endpointName, regionIds, productDomains));
  }

  static void updateEndpoint(string regionId, string product, string domain,
                             Endpoint* endpoint) {
    std::vector<ProductDomain> productDomains(endpoint->getProductDomains());
    ProductDomain* productDomain = findProductDomain(productDomains, product);
    if (NULL != productDomain) {
      productDomain->setDomainName(domain);
    } else {
      productDomains.push_back(ProductDomain(product, domain));
    }
    endpoint->setProductDomains(productDomains);
  }

  static Endpoint* findEndpointByRegionId(string regionId) {
    for (int i = 0; i < sEndpoints_.size(); i++) {
      if (sEndpoints_[i].getRegionIds().find(regionId)
          != sEndpoints_[i].getRegionIds().end()) {
        return &sEndpoints_[i];
      }
    }
    return NULL;
  }

  // return editable object
  static ProductDomain* findProductDomain(
      std::vector<ProductDomain>& productDomains, string product) {
    for (unsigned i = 0; i < productDomains.size(); i++) {
      if (::strncasecmp(product.c_str(),
                        productDomains[i].getProductName().c_str(),
                        product.length()) == 0) {
        return &productDomains[i];
      }
    }

    return NULL;
  }

  // return non editable object
  static const ProductDomain* findProductDomain(
      const std::vector<ProductDomain>& productDomains, string product) {
    for (unsigned i = 0; i < productDomains.size(); i++) {
      if (::strncasecmp(product.c_str(),
                        productDomains[i].getProductName().c_str(),
                        product.length()) == 0) {
        return &productDomains[i];
      }
    }

    return NULL;
  }

 private:

  DefaultProfile() {
    isigner_ = HmacSha1::getInstance();
    icredential_ = NULL;
    iendpoints_ = InternalEndpointsParser::getInstance();
    refreshed_ = false;
  }

  DefaultProfile(string region, Credential cred) {
    isigner_ = HmacSha1::getInstance();
    icredential_ = NULL;
    iendpoints_ = InternalEndpointsParser::getInstance();
    credential_ = cred;
    regionId_ = region;
    refreshed_ = false;
  }

  DefaultProfile(ICredentialProvider* icredential) {
    isigner_ = HmacSha1::getInstance();
    icredential_ = icredential;
    iendpoints_ = InternalEndpointsParser::getInstance();
    refreshed_ = false;
  }

  DefaultProfile(string region, ICredentialProvider* icredential) {
    regionId_ = region;
    isigner_ = HmacSha1::getInstance();
    icredential_ = icredential;
    iendpoints_ = InternalEndpointsParser::getInstance();
    refreshed_ = false;
  }

  DefaultProfile(ICredentialProvider* icredential, string region,
                 FormatType format) {
    regionId_ = region;
    acceptFormat_ = format;
    isigner_ = HmacSha1::getInstance();
    icredential_ = icredential;
    iendpoints_ = InternalEndpointsParser::getInstance();
    refreshed_ = false;
  }

  // disallow
  DefaultProfile(const DefaultProfile& rhs);

  // internal stuff only
  DefaultProfile& operator=(const DefaultProfile& rhs) {
    if (&rhs == this)
      return *this;
    regionId_ = rhs.regionId_;
    acceptFormat_ = rhs.acceptFormat_;
    credential_ = rhs.credential_;

    isigner_ = rhs.isigner_;
    iendpoints_ = rhs.iendpoints_;
    icredential_ = rhs.icredential_;
    return *this;
  }
 private:
  string regionId_;
  FormatType acceptFormat_;
  Credential credential_;
  bool refreshed_;

  // observer(not owner) pointers
  ISigner* isigner_;
  IEndpointsProvider* iendpoints_;
  ICredentialProvider* icredential_;

  static DefaultProfile* sProfile_;
  static DefaultProfile sInstanceCache_;
  static std::vector<Endpoint> sEndpoints_;
};

DefaultProfile* DefaultProfile::sProfile_ = NULL;

DefaultProfile DefaultProfile::sInstanceCache_;

std::vector<regions::Endpoint> DefaultProfile::sEndpoints_;

}  // namespace profile

}  // namespace aliyun

#endif  // CORE_PROFILE_DEFAULTPROFILE_H_
